﻿using System.Collections.Generic;
using GameLibrary;
using Microsoft.Xna.Framework;
using Rescue_of_the_Dwarvenzombies.Rendering;
using Rescue_of_the_Dwarvenzombies.Rendering.RenderStates;
using Rescue_of_the_Dwarvenzombies.Scene;
using Rescue_of_the_Dwarvenzombies.SceneLayers;
using Rescue_of_the_Dwarvenzombies.SceneObjects;
using Rescue_of_the_Dwarvenzombies.SceneObjects.GameObjects;
using Rescue_of_the_Dwarvenzombies.SceneObjects.GUIObjects;

namespace Rescue_of_the_Dwarvenzombies.GameStates
{
    /// <summary>
    /// The State active during actual gameplay.
    /// </summary>
    /// <remarks></remarks>
    internal class PlayingState : GameState
    {
        private PhysicsManager _physicsManager;
        private GUIHandler _guiHandler;
        private Vector2 _parallax;

        /// <summary>
        /// Gets the average player positions.
        /// </summary>
        /// <remarks></remarks>
        public Vector2 AveragePlayerPositions { get; private set; }

        /// <summary>
        /// Initializes a new instance of the <see cref="PlayingState"/> class.
        /// </summary>
        /// <param name="game">The game.</param>
        /// <param name="gameStateManager">The game state manager.</param>
        /// <remarks></remarks>
        public PlayingState(Game game, GameStateManager gameStateManager)
            : base(game)
        {
            GameStateManager = gameStateManager;
            _physicsManager = new PhysicsManager(game);
            _parallax = new Vector2(1f);
            InitRootLayer();
            _guiHandler = new GUIHandler(game);
        }

        /// <summary>
        /// Updates the specified game time.
        /// </summary>
        /// <param name="gameTime">The game time.</param>
        /// <remarks></remarks>
        public override void Update(GameTime gameTime)
        {
            if (HasStateChanged())
                return;

            RootLayer.Update(gameTime);
            _physicsManager.Update(gameTime);
            _guiHandler.Update(gameTime);

            CalcAveragePlayerPositions();

            ICamera camera = (ICamera)Game.Services.GetService(typeof(ICamera));
            camera.Update(AveragePlayerPositions, _parallax);
        }

        /// <summary>
        /// Calcs the average player positions.
        /// </summary>
        /// <remarks></remarks>
        private void CalcAveragePlayerPositions()
        {
            List<ISceneObject> players = RootLayer.Child.GetSceneObjects();

            float posSum = 0;
            foreach (ISceneObject player in players)
            {
                posSum += player.Position.X;
            }
            AveragePlayerPositions = new Vector2(posSum / players.Count, 0);
        }

        /// <summary>
        /// Determines whether [has state changed].
        /// </summary>
        /// <returns><c>true</c> if [has state changed]; otherwise, <c>false</c>.</returns>
        /// <remarks></remarks>
        protected override bool HasStateChanged()
        {
            IInputManager inputManager = (IInputManager)Game.Services.GetService(typeof(IInputManager));

            //If user has paused
            if (inputManager.CheckInput() == Constant.Action.Pause)
            {
                //change to paused state
                GameStateManager.ChangeState(new PauseState(Game, GameStateManager, RootLayer));
                //and paused render state
                RenderStateManager renderStateManager =
                    ((IRenderManager)Game.Services.GetService(typeof(IRenderManager))).RenderStateManager;
                renderStateManager.ChangeState(new PauseRenderState(Game, renderStateManager, renderStateManager.CurrentState));               
                return true;
            }

            //If no time is left
            if (!_guiHandler.IsTimeLeft())
            {
                //change to game over state
                GameStateManager.ChangeState(new GameOverState(Game, GameStateManager, RootLayer));
                //and game over render state
                RenderStateManager renderStateManager =
                    ((IRenderManager)Game.Services.GetService(typeof(IRenderManager))).RenderStateManager;
                renderStateManager.ChangeState(new GameOverRenderState(Game, renderStateManager, renderStateManager.CurrentState));
                return true;
            }
            return false;
        }

        /// <summary>
        /// Inits the root layer.
        /// </summary>
        /// <remarks></remarks>
        protected override sealed void InitRootLayer()
        {
            RootLayer = new RootLayer(Game);
        }

        /// <summary>
        /// Handles the collision.
        /// </summary>
        /// <param name="currentSceneObject">The current scene object.</param>
        /// <param name="childObject">The child object.</param>
        /// <param name="parentLayer">The parent layer.</param>
        /// <param name="childLayer">The child layer.</param>
        /// <remarks></remarks>
        public void HandleCollision(ISceneObject currentSceneObject, ISceneObject childObject, ISceneLayer parentLayer, ISceneLayer childLayer)
        {
            Constant.Layer parentLayerName = parentLayer.LayerName;
            Constant.Layer childLayerName = childLayer.LayerName;

            //Deal damage (players can't take damage from player weapons)
            if (childLayerName != Constant.Layer.Weapon && parentLayerName != Constant.Layer.Player)
                currentSceneObject.TakeDamage(childObject.GetDamage());

            //Check the layer type of the colliding scene objects to decide other consequences
            if (parentLayerName == Constant.Layer.Weapon)
            {
                if (childLayerName == Constant.Layer.Enemy)
                {
                    float damage = currentSceneObject.GetDamage();
                    childObject.TakeDamage(damage);
                    _guiHandler.IncreaseScore(damage);
                    ((ISoundManager)Game.Services.GetService(typeof(ISoundManager))).Play((GameObject)childObject, Constant.Action.TakeDamage);
                }
                //Do not remove weapon if collided with item
                if (childLayerName != Constant.Layer.Item)
                {
                    parentLayer.RemoveSceneObject(currentSceneObject);
                }
            }
            if (parentLayerName == Constant.Layer.Player)
            {
                //If a player "collides" with an item (potion), add time
                if (childLayerName == Constant.Layer.Item)
                {
                    AddTimeToTimeBar(Constant.POTION_RESTORE_POWER);
                    childLayer.RemoveSceneObject(childObject);
                }
                //If a player collides with an enemy, reduce time
                if (childLayerName == Constant.Layer.Enemy)
                {
                    ReduceTime(childObject.GetDamage());
                }
            }
        }

        /// <summary>
        /// Reduces the time.
        /// </summary>
        /// <param name="damage">The damage.</param>
        /// <remarks></remarks>
        private void ReduceTime(float damage)
        {
            _guiHandler.ReduceTime(damage);
        }

        /// <summary>
        /// Adds the time to time bar.
        /// </summary>
        /// <param name="time">The time.</param>
        /// <remarks></remarks>
        private void AddTimeToTimeBar(float time)
        {
            _guiHandler.IncreaseTime(time);
        }

        /// <summary>
        /// List of GUI objects.
        /// </summary>
        /// <returns>list of GUI objects</returns>
        /// <remarks></remarks>
        public override List<GameObject> GUIObjects()
        {
            return _guiHandler.GetGUIObjects();
        }

        /// <summary>
        /// Restarts the level.
        /// </summary>
        /// <remarks></remarks>
        public void RestartLevel()
        {
            InitRootLayer();
            _guiHandler.IncreaseTime(Constant.MAXTIME);
            ICamera camera = (ICamera)Game.Services.GetService(typeof(ICamera));
            camera.ResetPosition();
        }
    }
}